#include "LuaScriptState.h"

#include "Exception.h"
#include "swigluarun.h"
#include "Logger.h"

extern "C"
{
int luaopen_core(lua_State* L);
int luaopen_gui(lua_State* L);
int luaopen_input(lua_State* L);
int luaopen_vect(lua_State* L);
int luaopen_printing(lua_State* L);
int luaopen_res(lua_State* L);
int luaopen_sdl(lua_State* L);
int luaopen_audio(lua_State* L);
}

const char *LuaScriptState::METHOD_TABLE[] = {
    "Create",
    "Update",
    "OnKeyboardEvent",
    "OnMouseEvent",
    "Pause",
    "Resume",
    "Cleanup",
    "ActionPerformed",
    "OnEntityMessage"
};

LuaScriptState::LuaScriptState()
:
State(NULL)
{

}

LuaScriptState::LuaScriptState(EngineCore *engine, const std::string& script)
:
State(engine)
{
    m_context = lua_open( );

    int err;

    try
    {
        //load file and check for erros
        if( ( err = luaL_loadfile( m_context, script.c_str( ) ) ) != 0 )
        {

            if( err == LUA_ERRSYNTAX )
            {
                LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_ERROR ) << "Syntax error in file: " << script << "\n";
            }
            else if( err == LUA_ERRMEM )
            {
                LOG( Logger::CHANNEL_MAIN, LogFileStream::LEVEL_ERROR ) << "Cannot allocate memory for script: " << script << "\n";
            }
            else if( err == LUA_ERRFILE )
            {
                LOG( Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR ) << "Lua script file missing: " << script << "\n";
            }
        }

        //open libraries for scripting
        luaL_openlibs( m_context );
        luaopen_core( m_context );
        luaopen_input( m_context );
        luaopen_vect( m_context );
        luaopen_printing( m_context );
        luaopen_audio( m_context );
        luaopen_res( m_context );
        luaopen_sdl( m_context );
        luaopen_gui( m_context );
    }
    catch( char const* exc )
    {
        throw Exception( exc );
    }

    //call script to initialize script's global variables
    lua_pcall( m_context, 0, LUA_MULTRET, 0 );

    SWIG_NewPointerObj( m_context, this, SWIG_TypeQuery( m_context, "LuaScriptState*" ), 0 );

    lua_setglobal( m_context, "this" );

    CheckMethodDefinitions( );
}

LuaScriptState::~LuaScriptState()
{
    lua_close( m_context );
}

/**
 * Calls the Create function residing in the script.
 */

void LuaScriptState::Create()
{
    static int methodIndex = 0;

    if( m_methodDefinitions[methodIndex] )
    {
        int err = 0;

        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );

        lua_gettable( m_context, LUA_GLOBALSINDEX );

        if( ( err = lua_pcall( m_context, 0, 0, 0 ) ) != 0 )
        {
            PrintExecError( methodIndex, err );
        }
    }
}

/**
 * Updates the State by calling the correspondive method in the lua script.
 * The standard Update must occurr first (Entity, Rendering and Physics managers).
 *
 * @param delta: time elapsed since last call.
 */

void LuaScriptState::Update(unsigned delta)
{
    static int methodIndex = 1;

    State::Update( delta );

    if( m_methodDefinitions[methodIndex] )
    {
        int err = 0;

        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );

        lua_gettable( m_context, LUA_GLOBALSINDEX );

        lua_pushnumber( m_context, delta );

        if( ( err = lua_pcall( m_context, 1, 0, 0 ) ) != 0 )
        {
            PrintExecError( methodIndex, err );
        }
    }
}

void LuaScriptState::OnEntityMessage(EntitySystem::Entity* sender, const std::string& msg)
{
    static int methodIndex = 8;

    //State::OnEntityMessage( sender, msg );

    if( m_methodDefinitions[methodIndex] )
    {
        int err = 0;

        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );

        lua_gettable( m_context, LUA_GLOBALSINDEX );

        SWIG_NewPointerObj( m_context, sender, SWIG_TypeQuery( m_context, "EntitySystem::Entity*" ), 0 );

        lua_pushstring( m_context, msg.c_str( ) );

        if( ( err = lua_pcall( m_context, 2, 0, 0 ) ) != 0 )
        {
            PrintExecError( methodIndex, err );
        }
    }
}

/**
 * Pushes keyboard events to the Entities. Then it forwards keyboard event to the script.
 *
 * @param event: a keyboard event occurred.
 */

void LuaScriptState::OnKeyboardEvent(KeyboardEvent& event)
{
    static int methodIndex = 2;

    State::OnKeyboardEvent( event );

    if( m_methodDefinitions[methodIndex] )
    {
        int err = 0;

        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );

        lua_gettable( m_context, LUA_GLOBALSINDEX );

        SWIG_NewPointerObj( m_context, &event, SWIG_TypeQuery( m_context, "KeyboardEvent*" ), 0 );

        if( ( err = lua_pcall( m_context, 1, 0, 0 ) ) != 0 )
        {
            PrintExecError( methodIndex, err );
        }
    }
}

/**
 * Calls regular State Update. Then forwards the mouse event to the script. 
 * 
 * @param event: a mouse event occurred. 
 */

void LuaScriptState::OnMouseEvent(MouseEvent& event)
{
    static int methodIndex = 3;

    //State::OnMouseEvent( event );

    if( m_methodDefinitions[methodIndex] )
    {
        int err = 0;

        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );

        lua_gettable( m_context, LUA_GLOBALSINDEX );

        SWIG_NewPointerObj( m_context, &event, SWIG_TypeQuery( m_context, "MouseEvent*" ), 0 );

        if( ( err = lua_pcall( m_context, 1, 0, 0 ) ) != 0 )
        {
            PrintExecError( methodIndex, err );
        }
    }
}

/**
 * Calls the pause function implemented in script.
 */

void LuaScriptState::Pause()
{
    static int methodIndex = 4;

    if( m_methodDefinitions[methodIndex] )
    {
        int err = 0;

        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );

        lua_gettable( m_context, LUA_GLOBALSINDEX );

        if( ( err = lua_pcall( m_context, 0, 0, 0 ) ) != 0 )
        {
            PrintExecError( methodIndex, err );
        }
    }
}

/**
 * Calls the resume function implemented in script.
 */

void LuaScriptState::Resume()
{
    static int methodIndex = 5;

    if( m_methodDefinitions[methodIndex] )
    {
        int err = 0;

        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );

        lua_gettable( m_context, LUA_GLOBALSINDEX );

        if( ( err = lua_pcall( m_context, 0, 0, 0 ) ) != 0 )
        {
            PrintExecError( methodIndex, err );
        }
    }
}

/**
 * Calls the cleanup function implemented in script.
 */

void LuaScriptState::Cleanup()
{
    static int methodIndex = 6;

    if( m_methodDefinitions[methodIndex] )
    {
        int err = 0;

        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );

        lua_gettable( m_context, LUA_GLOBALSINDEX );

        if( ( err = lua_pcall( m_context, 0, 0, 0 ) ) != 0 )
        {
            PrintExecError( methodIndex, err );
        }
    }
}

/**
 * Called when an action is performed on a gui component belonging to this State.
 *
 * @param sender: gui component that triggered the action.
 */

void LuaScriptState::ActionPerformed(gui::Component *sender)
{
    static int methodIndex = 7;

    if( m_methodDefinitions[methodIndex] )
    {
        int err = 0;

        lua_pushstring( m_context, METHOD_TABLE[methodIndex] );

        lua_gettable( m_context, LUA_GLOBALSINDEX );

        SWIG_NewPointerObj( m_context, sender, SWIG_TypeQuery( m_context, "gui::Component*" ), 0 );

        if( ( err = lua_pcall( m_context, 1, 0, 0 ) ) != 0 )
        {
            PrintExecError( methodIndex, err );
        }
    }
}

/** 
 * Checks which methods of State are implemented in the script.
 */

void LuaScriptState::CheckMethodDefinitions()
{
    for( int i = 0; i < N_METHODS; ++i )
    {
        lua_getglobal( m_context, METHOD_TABLE[i] );

        m_methodDefinitions[i] = lua_isfunction( m_context, lua_gettop( m_context ) ) ? true : false;

        lua_setglobal( m_context, METHOD_TABLE[i] );
    }
}

/**7
 * Prints an error occurred when executing a script function. 
 *
 * @param methodIndex: index (in METHOD_TABLE) of the function that failed to execute.
 * @param errorCode: error code returned by lua.
 */

void LuaScriptState::PrintExecError(int methodIndex, int errorCode)
{
    LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_ERROR ) << "Could not invoke " << METHOD_TABLE[methodIndex] << " in a State script! Reason: ";

    const char *error = lua_tostring( m_context, lua_gettop( m_context ) );

    if( errorCode == LUA_ERRRUN )
    {
        LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_ERROR ) << " Runtime Error: " << error << '\n';
    }
    else if( errorCode == LUA_ERRMEM )
    {
        LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_ERROR ) << " Memory Error: " << error << '\n';
    }
    else
    {
        LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_ERROR ) << " Error: " << error << '\n';
    }
}